// SPDX-License-Identifier: GPL-2.0

#include <linux/kernel.h>
#include <linux/slab.h>
#include "dim_json.h"

struct dim_json_context {
	char *pos;
	long remain;
};

static int dim_json_unmarshal_object(struct dim_json_context *ctx, struct dim_json_object *obj);
static int dim_json_unmarshal_array(struct dim_json_context *ctx, struct dim_json_object *obj);
static int dim_json_unmarshal_string(struct dim_json_context *ctx, struct dim_json_object *obj);

/*
 * move cursor backwards, skipping space characters
 */
static char next_char(struct dim_json_context *ctx)
{
	if (ctx == NULL || ctx->pos == NULL || ctx->remain == 0)
		return '\0';

	while (ctx->remain > 0) {
		ctx->remain--;
		ctx->pos++;

		if (*(ctx->pos) != ' ' &&
		    *(ctx->pos) != '\r' &&
		    *(ctx->pos) != '\n' &&
		    *(ctx->pos) != '\t' &&
		    *(ctx->pos) != '\v' &&
		    *(ctx->pos) != '\f')
			return *(ctx->pos);
	}

	return '\0';
}

static struct dim_json_object *dim_json_object_create(void)
{
	struct dim_json_object *obj = NULL;

	obj = kzalloc(sizeof(struct dim_json_object), GFP_KERNEL);
	if (obj == NULL)
		return NULL;

	obj->type = DIM_JSON_UNKNOWN;
	return obj;
}

static int dim_json_get_key(struct dim_json_context *ctx, struct dim_json_object *obj)
{
	char *str = NULL;

	if (ctx == NULL || ctx->pos == NULL || ctx->remain == 0)
		return '\0';

	/* start from '"' */
	str = (++ctx->pos);
	ctx->remain--;

	while (ctx->remain > 0) {
		if (*ctx->pos == '\\') {
			ctx->pos++;
			ctx->remain--;
		} else if (*ctx->pos == '"') {
			*ctx->pos = '\0';
			if (next_char(ctx) != ':')
				return -1;
			obj->key = str;
			next_char(ctx);
			return 0; /* parse ok */
		}

		ctx->pos++;
		ctx->remain--;
	}

	return -1;
}

static int dim_json_unmarshal_string(struct dim_json_context *ctx, struct dim_json_object *obj)
{
	char *str = NULL;

	if (ctx == NULL || ctx->pos == NULL || ctx->remain == 0)
		return '\0';

	/* start from '"' */
	str = (++ctx->pos);
	ctx->remain--;

	while (ctx->remain > 0) {
		if (*ctx->pos == '\\') {
			ctx->pos++;
			ctx->remain--;
		} else if (*ctx->pos == '"') {
			*ctx->pos = '\0';
			obj->value = str;
			next_char(ctx);
			return 0; /* parse ok */
		}

		ctx->pos++;
		ctx->remain--;
	}

	return -1;
}

static int dim_json_unmarshal_array(struct dim_json_context *ctx, struct dim_json_object *obj)
{
	struct dim_json_object *p_obj = NULL;
	struct dim_json_object *sub_obj = NULL;
	enum dim_json_type type = DIM_JSON_UNKNOWN;
	int ret;

	if (ctx == NULL || ctx->pos == NULL || ctx->remain == 0)
		return '\0';

	if (next_char(ctx) == ']') {
		next_char(ctx);
		return 0; /* blank array */
	}

	while (ctx->remain > 0) {
		sub_obj = dim_json_object_create();
		if (sub_obj == NULL)
			return -1;

		if (p_obj == NULL)
			obj->value = (p_obj = sub_obj);
		else
			p_obj = (p_obj->next = sub_obj);

		switch (*ctx->pos) {
		case '"':
			ret = dim_json_unmarshal_string(ctx, sub_obj);
			sub_obj->type = DIM_JSON_STRING;
			break;
		case '{':
			ret = dim_json_unmarshal_object(ctx, sub_obj);
			sub_obj->type = DIM_JSON_OBJECT;
			break;
		case '[':
			ret = dim_json_unmarshal_array(ctx, sub_obj);
			sub_obj->type = DIM_JSON_ARRAY;
			break;
		default:
			return -1;
		}

		if (ret != 0)
			return ret;

		/* the type of all object in an array should be same */
		if (type == DIM_JSON_UNKNOWN)
			type = p_obj->type;
		else if (type != p_obj->type)
			return -1;

		if (*ctx->pos == ']') {
			next_char(ctx);
			return 0;
		} else if (*ctx->pos != ',') {
			return -1;
		}

		next_char(ctx);
	}

	return -1;
}

static int dim_json_unmarshal_object(struct dim_json_context *ctx,
				     struct dim_json_object *obj)
{
	struct dim_json_object *p_obj = NULL;
	struct dim_json_object *sub_obj = NULL;
	int ret;

	if (ctx == NULL || ctx->pos == NULL || ctx->remain == 0)
		return '\0';

	if (next_char(ctx) == '}') {
		next_char(ctx);
		return 0; /* blank object */
	}

	while (ctx->remain > 0) {
		sub_obj = dim_json_object_create();
		if (sub_obj == NULL)
			return -1;

		if (p_obj == NULL)
			obj->value = (p_obj = sub_obj);
		else
			p_obj = (p_obj->next = sub_obj);

		ret = dim_json_get_key(ctx, sub_obj);
		if (ret != 0)
			return ret;

		switch (*ctx->pos) {
		case '"':
			ret = dim_json_unmarshal_string(ctx, sub_obj);
			sub_obj->type = DIM_JSON_STRING;
			break;
		case '{':
			ret = dim_json_unmarshal_object(ctx, sub_obj);
			sub_obj->type = DIM_JSON_OBJECT;
			break;
		case '[':
			ret = dim_json_unmarshal_array(ctx, sub_obj);
			sub_obj->type = DIM_JSON_ARRAY;
			break;
		default:
			return -1;
		}

		if (ret != 0)
			return ret;

		if (*ctx->pos == '}') {
			next_char(ctx);
			return 0;
		} else if (*ctx->pos != ',') {
			return -1;
		}

		next_char(ctx);
	}

	return -1;
}

struct dim_json_object *dim_json_unmarshal(char *buf, unsigned long buf_len)
{
	int ret;
	struct dim_json_object *root = NULL;
	struct dim_json_context ctx = {
		.pos = buf,
		.remain = buf_len - 1
	};

	if (buf == NULL || buf_len == 0)
		return ERR_PTR(-EINVAL);

	root = dim_json_object_create();
	if (root == NULL)
		return ERR_PTR(-ENOMEM);

	root->type = DIM_JSON_OBJECT;

	ret = dim_json_unmarshal_object(&ctx, root);
	if (ret != 0) {
		dim_json_free(root);
		return ERR_PTR(-EINVAL);
	}

	return root;
}

struct dim_json_object *dim_json_get_object(struct dim_json_object *obj,
					    const char *key,
					    enum dim_json_type type)
{
	struct dim_json_object *p_obj = NULL;

	if (key == NULL || obj == NULL || obj->type != DIM_JSON_OBJECT ||
	    type < 0 || type >= DIM_JSON_UNKNOWN)
		return NULL;

	for (p_obj = (struct dim_json_object *)obj->value;
	     p_obj != NULL;
	     p_obj = p_obj->next) {
		if (p_obj->type == type && (strcmp(p_obj->key, key) == 0))
			return p_obj;
	}

	return NULL;
}

int dim_json_iterator_array(struct dim_json_object *obj,
			    dim_json_iterator f,
			    void *data)
{
	struct dim_json_object *p_obj = NULL;
	int ret;

	if (obj == NULL || f == NULL || obj->type != DIM_JSON_ARRAY)
		return -EINVAL;

	for (p_obj = (struct dim_json_object *)obj->value;
	     p_obj != NULL;
	     p_obj = p_obj->next) {
		ret = f(p_obj, data);
		if (ret != 0)
			return ret;
	}

	return 0;
}

void dim_json_free(struct dim_json_object *obj)
{
	struct dim_json_object *p_obj = NULL;
	struct dim_json_object *t_obj = NULL;

	if (obj == NULL)
		return;

	if (obj->type != DIM_JSON_STRING && obj->type != DIM_JSON_UNKNOWN) {
		/* do free recursively */
		p_obj = (struct dim_json_object *)obj->value;
		while (p_obj != NULL) {
			t_obj = p_obj->next;
			dim_json_free(p_obj);
			p_obj = t_obj;
		}
	}

	kfree(obj);
}
